﻿using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using System.Text;
using System.Text.RegularExpressions;
using XYS.Core.Models;
using XYS.Core.Util.Helper;

namespace XYS.Core.Api.Middlewares
{
    /// <summary>
    /// 拦截请求 中间件
    /// TODO：参数的加解密
    /// </summary>
    public class HttpContextMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly IServiceScopeFactory scopeFactory;
        private static List<IHttpStreamHandle> httpStreamHandles = null;

        /// <summary>
        /// 拦截请求 中间件
        /// </summary>
        /// <param name="next"></param>
        /// <param name="scopeFactory"></param>
        public HttpContextMiddleware(RequestDelegate next, IServiceScopeFactory scopeFactory)
        {
            _next = next;
            if (httpStreamHandles == null)
            {
                //反射获取 IHttpStreamHandles
                var httpStreamHandleTypes = AssemblyHelper.GetAssemblyTypes()
                    .Where(t => !t.IsInterface && typeof(IHttpStreamHandle).IsAssignableFrom(t))
                    .ToList();

                if (httpStreamHandleTypes.Count > 0)
                    httpStreamHandles = httpStreamHandleTypes.Select(e => e.Assembly.CreateInstance(e.FullName) as IHttpStreamHandle)
                        .Where(e => e.PriorityLevel >= 0)
                        .OrderByDescending(e => e.PriorityLevel)
                        .ToList();
                else
                    httpStreamHandles = new List<IHttpStreamHandle>();
            }

            this.scopeFactory = scopeFactory;
        }

        /// <summary>
        /// Invoke
        /// </summary>
        /// <param name="httpContext"></param>
        /// <returns></returns>
        public async Task Invoke(HttpContext httpContext)
        {
            if (GlobalCoreSetting.FormatRequestsIgnore.Any(x => Regex.IsMatch(httpContext.Request.Path.Value, "^" + Regex.Escape(x).Replace("\\*", ".*") + "$")))
            {
                await _next(httpContext).ConfigureAwait(false);
                return;
            }

            if (httpContext.Request.Path.Value.StartsWith("/api/health/check"))
            {
                await _next(httpContext).ConfigureAwait(false);
                return;
            }
            if (httpContext.Request.Path.Value.StartsWith("/files"))
            {
                await _next(httpContext).ConfigureAwait(false);
                return;
            }
            else if (httpContext.Request.Path.Value.StartsWith("/run"))
            {
                await _next(httpContext).ConfigureAwait(false);
                return;
            }
            else if (httpContext.Request.Path.Value.StartsWith("/log"))
            {
                await _next(httpContext).ConfigureAwait(false);
                return;
            }
            else if (httpContext.Request.Path.Value.StartsWith("/beat"))
            {
                await _next(httpContext).ConfigureAwait(false);
                return;
            }
            else if (httpContext.Request.ContentType != null)
            {
                if (httpContext.Request.ContentType.ToLower().Contains("grpc"))
                {
                    await _next(httpContext).ConfigureAwait(false);
                    return;
                }
            }

            bool isNext = false;
            var originResponseBody = httpContext.Response.Body;
            try
            {
                if (httpStreamHandles != null)
                {
                    //读取 requestBody
                    string requestBody = "";
                    httpContext.Request.EnableBuffering();
                    using var requestReader = new StreamReader(httpContext.Request.Body);
                    requestBody = await requestReader.ReadToEndAsync();
                    httpContext.Request.Body.Position = 0;
                    string newResqustBody = requestBody;

                    using var scope = scopeFactory.CreateScope();
                    Dictionary<IHttpStreamHandle, object> resultMaps = new();
                    foreach (var httpStreamHandle in httpStreamHandles)
                    {
                        resultMaps[httpStreamHandle] = httpStreamHandle.Invoke(scope, httpContext, newResqustBody, out newResqustBody);
                    }
                    //更新 requestBody
                    if (newResqustBody != requestBody)
                    {
                        using var newRequest = new MemoryStream();
                        var writer = new StreamWriter(newRequest);
                        await writer.WriteAsync(newResqustBody);
                        await writer.FlushAsync();
                        newRequest.Position = 0;
                        httpContext.Request.ContentLength = Encoding.Default.GetBytes(newResqustBody).Length;
                        httpContext.Request.Body = newRequest;
                    }

                    using var memStream = new MemoryStream();
                    httpContext.Response.Body = memStream;

                    //next
                    isNext = true;
                    await _next(httpContext).ConfigureAwait(false);
                    memStream.Position = 0;//指针还原
                                           //读取 responseBody
                    var responseBody = new StreamReader(memStream).ReadToEnd();

                    string newResponseBody = responseBody;
                    //Nexted
                    foreach (var httpStreamHandle in resultMaps.Keys)
                    {
                        newResponseBody = httpStreamHandle.Nexted(scope, httpContext, newResponseBody, resultMaps[httpStreamHandle]);
                    }

                    //更新responseBody
                    httpContext.Response.ContentLength = Encoding.Default.GetBytes(newResponseBody).Length;
                    using var memoryStreamModified = new MemoryStream();
                    using var sw = new StreamWriter(memoryStreamModified);
                    sw.Write(newResponseBody);
                    sw.Flush();
                    memoryStreamModified.Position = 0;
                    await memoryStreamModified.CopyToAsync(originResponseBody).ConfigureAwait(false);

                    //流程结束
                    foreach (var httpStreamHandle in resultMaps.Keys)
                    {
                        httpStreamHandle.ProEnd(scope, httpContext, resultMaps[httpStreamHandle]);
                    }
                }
            }
            finally
            {
                httpContext.Response.Body = originResponseBody;
                if (!isNext) await _next(httpContext).ConfigureAwait(false);
            }
        }

    }
}
